// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022.
 * Description: /dev/module_crl device support.
 * Create: 2022-03-01
 */

#include <linux/device.h>
#include <linux/file.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/crl_revocate.h>
#include "cmscbb_cms_vrf.h"

#define MODULE_CRL_DEVICE "module_crl"

static DEFINE_MUTEX(crl_mutex);

static DEFINE_MUTEX(crl_bundle_toupdate_mutex);
crl_bundle_buffer crl_bundle_toupdate;

static bool open_flag;
static DEFINE_MUTEX(crl_open_flag);

static int crl_working;

struct crl_private_data {
	bool crl_written;
};

static struct class *module_crl_class;
static int module_crl_major;

static DECLARE_WAIT_QUEUE_HEAD(crl_poll_wait);

static unsigned int dev_crl_poll(struct file *file, struct poll_table_struct *wait)
{
	unsigned int mask = POLLOUT | POLLWRNORM;

	poll_wait(file, &crl_poll_wait, wait);
	if (crl_working == 1)
		mask |= POLLIN | POLLRDNORM;
	crl_working = 0;
	return mask;
}

static ssize_t dev_crl_write(struct file *file, const char __user *buf,
				size_t datalen, loff_t *ppos)
{
	ssize_t len = -EINVAL;
	struct crl_private_data *priv = file->private_data;

	mutex_lock(&crl_bundle_toupdate_mutex);

	if (!priv->crl_written) {
		memset(crl_bundle_toupdate.buf, 0, crl_bundle_toupdate.size);
		crl_bundle_toupdate.size = 0;
	}

	if (datalen >= MAX_CRL_SIZE - crl_bundle_toupdate.size)
		goto out;

	len = simple_write_to_buffer(crl_bundle_toupdate.buf, MAX_CRL_SIZE, ppos, buf, datalen);
	if (len < 0)
		goto out;

	crl_bundle_toupdate.size += len;
	priv->crl_written = true;

out:
	mutex_unlock(&crl_bundle_toupdate_mutex);
	return len;
}

static int dev_crl_open(struct inode *inode, struct file *file)
{
	struct crl_private_data *priv = NULL;

	mutex_lock(&crl_open_flag);
	if (open_flag == true) {
		pr_err("modsign: Cannot open multiple files at the same time\n");
		mutex_unlock(&crl_open_flag);
		return -ENOMEM;
	}
	open_flag = true;
	mutex_unlock(&crl_open_flag);

	priv = (struct crl_private_data *)kmalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	file->private_data = priv;
	priv->crl_written = false;

	return 0;
}

static ssize_t dev_crl_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	ssize_t len;

	mutex_lock(&crl_mutex);
	len = simple_read_from_buffer(buf, count, ppos, get_crl_ondevice_buf(), get_crl_ondevice_size());
	mutex_unlock(&crl_mutex);
	return len;
}

static void crl_free(CMSCBB_VRF_CTX *vrf_ctx, CmscbbCrlBundleInfo **crl_info_toupdate,
			CmscbbCrlBundleInfo **crl_info_ondevice)
{
	if (*vrf_ctx) {
		if (*crl_info_ondevice)
			(void)CmscbbCrlFree(*vrf_ctx, *crl_info_ondevice);
		if (*crl_info_toupdate)
			(void)CmscbbCrlFree(*vrf_ctx, *crl_info_toupdate);
		(void)CmscbbVrfCtxFree(*vrf_ctx);
	}
}

static int crl_check_toupdate(CMSCBB_VRF_CTX *vrf_ctx, CmscbbCrlBundleInfo **crl_info_toupdate)
{
	int crl_count = 0;
	CMSCBB_ERROR_CODE cms_ret = CVB_TRUE;
	CmscbbCtxCreateParams cxt_params = {0};

	cxt_params.st_size = sizeof(cxt_params);
	cms_ret = CmscbbVerifyCreateCtx(vrf_ctx, &cxt_params);
	if (cms_ret) {
		pr_err("modsign: Failed to creat ctx cms_ret: 0x%x\n", CMS_RET_FORMAT(cms_ret));
		return -ENOKEY;
	}

	if (get_crl_cert_size() == 0 || !get_crl_cert()) {
		pr_err("modsign: Cannot find cert file.\n");
		return -ENOKEY;
	}

	cms_ret = CmscbbVerifyAddCert(*vrf_ctx, get_crl_cert(), get_crl_cert_size());
	if (cms_ret) {
		pr_err("modsign: Failed to add certificate at init cms_ret: 0x%x\n", CMS_RET_FORMAT(cms_ret));
		return -ENOKEY;
	}

	cms_ret = CmscbbDecodeCrl(*vrf_ctx, crl_bundle_toupdate.buf,
				crl_bundle_toupdate.size, crl_info_toupdate);
	if (cms_ret) {
		pr_err("modsign: Failed to decode crl file, cms_ret: 0x%x\n", CMS_RET_FORMAT(cms_ret));
		return -EINVAL;
	}

	if ((*crl_info_toupdate)->crlCount <= 0) {
		pr_err("modsign: empty input crl bundle.\n");
		return -EINVAL;
	}

	for (crl_count = 0; crl_count < (*crl_info_toupdate)->crlCount; crl_count++) {
		if ((*crl_info_toupdate)->crlInfoList[crl_count]->valid != CVB_TRUE) {
			pr_err("modsign: Crl bundle mismatches with current certificate.\n");
			return -EINVAL;
		}
	}

	return 0;
}

static int crl_ondevice_update(CMSCBB_VRF_CTX *vrf_ctx, CmscbbCrlBundleInfo **crl_info_toupdate,
			CmscbbCrlBundleInfo **crl_info_ondevice)
{
	int err = -EINVAL;
	CmscbbCrlPeriodStat compare_stat = SCPS_MIX;
	CMSCBB_ERROR_CODE cms_ret = CVB_TRUE;
	mutex_lock(&crl_mutex);

	if (get_crl_ondevice_size() != 0) {
		cms_ret = CmscbbDecodeCrl(*vrf_ctx, get_crl_ondevice_buf(),
				get_crl_ondevice_size(), crl_info_ondevice);
		if (cms_ret) {
			pr_err("modsign: Failed to decode crl file, cms_ret: 0x%x\n", CMS_RET_FORMAT(cms_ret));
			goto out;
		}

		cms_ret = CmscbbCrlCompare(*vrf_ctx, *crl_info_toupdate, *crl_info_ondevice, &compare_stat);
		if (cms_ret) {
			pr_err("modsign: CompareCrl Fail cms_ret: 0x%x.\n", CMS_RET_FORMAT(cms_ret));
			goto out;
		}

		switch (compare_stat) {
		case SCPS_NEW:
			goto out_update;
		case SCPS_SAME:
			pr_info("modsign: same crl, stop updating.\n");
			/* fallthrough */
		default:
			pr_info("modsign: no newer crl file, please check.\n");
			goto out;
		}
	}
out_update:
	err = 0;
	copyto_crl_ondevice_buf(crl_bundle_toupdate.buf, crl_bundle_toupdate.size);
	set_crl_ondevice_size(crl_bundle_toupdate.size);

	/* Update latest crl for module crl verification */
	err = mod_update_crl(get_crl_ondevice_buf(), get_crl_ondevice_size());
	if (err != 0)
		goto out;
	pr_info("modsign: new crl, updated successfully.\n");
	crl_working = 1;
	wake_up_interruptible(&crl_poll_wait);
out:
	mutex_unlock(&crl_mutex);
	return err;
}

static int dev_crl_release(struct inode *inode, struct file *file)
{
	struct crl_private_data *priv = file->private_data;
	CMSCBB_VRF_CTX vrf_ctx = NULL;
	int err = -EINVAL;
	CmscbbCrlBundleInfo *crl_info_toupdate = NULL;
	CmscbbCrlBundleInfo *crl_info_ondevice = NULL;

	if (!priv->crl_written || crl_bundle_toupdate.size >= MAX_CRL_SIZE)
		goto out;

	err = crl_check_toupdate(&vrf_ctx, &crl_info_toupdate);
	if (err)
		goto out;

	err = crl_ondevice_update(&vrf_ctx, &crl_info_toupdate, &crl_info_ondevice);
out:
	open_flag = false;
	crl_free(&vrf_ctx, &crl_info_toupdate, &crl_info_ondevice);
	kfree(file->private_data);
	file->private_data = NULL;

	return err;
}

static const struct file_operations dev_crl_fops = {
	.open = dev_crl_open,
	.release = dev_crl_release,
	.read = dev_crl_read,
	.write = dev_crl_write,
	.poll = dev_crl_poll,
	.llseek = generic_file_llseek,
};

static __init int crl_dev_init(void)
{
	struct device *class_dev = NULL;
	int ret = 0;

	/* module_crl will dynamically allocate a major munber */
	module_crl_major = register_chrdev(0, MODULE_CRL_DEVICE, &dev_crl_fops);
	if (module_crl_major < 0) {
		pr_err("modsign: unable to get major %d for memory devs.\n", module_crl_major);
		return -1;
	}

	module_crl_class = class_create(THIS_MODULE, MODULE_CRL_DEVICE);
	if (IS_ERR(module_crl_class)) {
		ret = PTR_ERR(module_crl_class);
		goto error_class_create;
	}

	class_dev = device_create(module_crl_class, NULL, MKDEV((unsigned int)module_crl_major, 1),
				NULL, MODULE_CRL_DEVICE);
	if (unlikely(IS_ERR(class_dev))) {
		ret = PTR_ERR(class_dev);
		goto error_device_create;
	}
	pr_info("modsign: create major %d for module crl devs\n", module_crl_major);
	return 0;

error_device_create:
	class_destroy(module_crl_class);
error_class_create:
	unregister_chrdev(module_crl_major, MODULE_CRL_DEVICE);
	return ret;
}

module_init(crl_dev_init);

static void __exit crl_dev_exit(void)
{
	device_destroy(module_crl_class, MKDEV((unsigned int)module_crl_major, 1));
	class_destroy(module_crl_class);
	unregister_chrdev(module_crl_major, MODULE_CRL_DEVICE);
}

module_exit(crl_dev_exit);
MODULE_AUTHOR("Huawei");
MODULE_DESCRIPTION("module_crl device");
MODULE_LICENSE("Dual BSD/GPL");
